﻿/*
 * TIFFCodec
 * 
 * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Marco Schmidt.
 * All rights reserved.
 * 
 * C# translation and update 
 * Copyright (c) Adam Sida, asida@centrum.cz
 * 
 * All rights reserved.
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
//using System.Windows.Media.Imaging;



namespace Petra.Gis.Geometry.Image.Tiff
{
  ///<summary>
  /// codec that reads Tagged Image File Format (TIFF) image files.
  /// All know type of tag is recognized and all known color schemes is recognized
  /// all known data configuration is supported
  /// only some compression types are supported
  ///</summary>
  public class TiffBitmapDecoder //: BitmapDecoder
  {

    private static Hashtable _compressions = new Hashtable();
    //private delegate TiffCompression getCompression(BlockBitmapFrame image);

    private BlockBitmapFrame[] _frames;
    private TiffDataEndian _endian; //instead of byteOrder
    private long _nextIfdOffset;  //position of current ifd table
    private int _firstIfdOffset; //position of first ifd table
    private Stream _tiffStream; //base data stream
    private TiffReader _tiffReader;

    private bool _loadBitonalAsPaletted8bpp = true;

    #region constructors


    private TiffBitmapDecoder()
    {
      //register available compressions
      RegisterCompression(new TiffCompressionDeflate());
      //registerDecoder(new TIFFDecoderModifiedHuffman());
      RegisterCompression(new TiffCompressionPackbits());
      RegisterCompression(new TiffCompressionNone());
      RegisterCompression(new TiffCompressionLogLuv());
      RegisterCompression(new TiffCompressionCCITT());
      RegisterCompression(new TiffCompressionJpeg());
    }


    public TiffBitmapDecoder(string FileName)
      :this()
    {
      _tiffStream = new FileStream(FileName, FileMode.Open, FileAccess.Read);

      //process()
      try
      {
        //parse whole tiff and create all necessary structs
        //but dont load any bitmap data
        readTiffImageFile();
      }
      catch (IOException ioe)
      {
        _tiffStream.Close();
        throw new InvalidOperationException("I/O error occurred: " + ioe.ToString());
      }
    }


    #endregion constructors


    #region public members



    internal TiffCompression GetCompression(BlockBitmapFrame currentImage)
    {
      TiffCompression dataCompression;

      try
      {
        dataCompression = (TiffCompression)_compressions[currentImage.TiffMetadata.Compression];
      }
      catch
      {
        throw new ArgumentException("Could not create decoder for this compression type: " +
          currentImage.TiffMetadata.Compression.ToString());
      }

      return dataCompression;

    }


    ///<summary>
    /// Register a TiffCompression class.
    /// TIFF knows many compression types, and this codec only supports some of them.
    /// To register an external TiffCompression class with TIFFCodec, call this method
    /// with the class field of your decoder.
    /// As an example, for your TIFFDecoderMyCompression class,
    /// call <code>TIFFCodec.registerDecoder(TIFFDecoderMyCompression.class)</code>.
    /// It will be checked if 
    /// <code>decoderClass.newInstance() instanceof TiffCompression</code>
    /// is true and, if so, the class will be added to an internal list.
    /// Whenever a TIFF file is to be decoded, the correct decoder is determined
    /// (each decoder knows about the compression types it supports via the getCompressionTypes method)
    /// and for each tile or strip such a decoder object will be created.
    ///</summary>
    public void RegisterCompression(TiffCompression newCompression)
    {

      if (newCompression == null)
        return;

      CompressionType[] ctypes = newCompression.CompressionTypes;

      if (ctypes.Length < 1)
        throw new ArgumentException("invalid decoder, decoder supports no compression");

      //int index = 0;
      //while (index < newCompression.CompressionTypes.Length)
      foreach (CompressionType ctype in ctypes)
      {
        _compressions.Add(ctype, newCompression);
      }
    }


    public String FormatName
    {
      get { return "Tagged Image File Format (TIFF)"; }
    }




    public String[] MimeTypes
    {
      get { return new String[] { "image/tiff", "image/tif" }; }
    }




    public bool IsLoadingSupported
    {
      get { return true; }
    }




    public bool IsSavingSupported
    {
      get { return false; }

    }



    ///<summary>
    /// Sets the byte order to the argument.
    /// The byte order in a TIFF file is either {@link #BYTE_ORDER_INTEL} or
    /// {@link #BYTE_ORDER_MOTOROLA}.
    /// @param newByteOrder the new byte order to be set
    /// @throws ArgumentException if the argument is not one of the above
    ///  mentioned constants
    ///</summary>
    public TiffDataEndian Endian
    {
      get { return _endian; }
    }


    ///<summary>
    /// Returns the image object stored in this codec.
    /// This is either an image given to this object via
    /// {@link #setImage(PixelImage)} or it was created by the codec
    /// itself during a loading operation.
    /// @return PixelImage object stored in this codec
    ///</summary>
    public BlockBitmapFrame[] Frames
    {
      get { return _frames; }
      //set { image = value; }
    }



    internal TiffReader TiffReader
    {
      get { return _tiffReader; }
    }


    public bool LoadBitonalAsPaletted8bpp
    {
      get { return _loadBitonalAsPaletted8bpp; }
      set { _loadBitonalAsPaletted8bpp = value; }
    }


    #endregion public members


    #region private members


    /**
     * Attempts to load an image from a file in the TIFF format.
     * Some options can be given to this codec before the call
     * to this load method.
     * <ul>
     * <li>You must provide a {@link java.io.RandomAccessFile} using
     *   {@link #setInput(java.io.RandomAccessFile)}.</li>
     * <li>If there is more than one image in the input file, you
     *   can make the codec load it by calling {@link #setImageIndex(int)}.
     *   The argument is the index of the image, the first being <code>0</code>,
     *   the second <code>1</code> and so on. The default is <code>0</code>.</li>
     * </ul>
     *
     * @return the image if everything was successful
     * @throws InvalidFileStructureException if the TIFF file was corrupt in some way
     * @throws IOException if there were errors reading from the input file
     * @throws UnsupportedTypeException if the flavour of TIFF encountered in the input
     *  file is not supported yet
     * @throws WrongFileFormatException
     */
    private void readTiffImageFile()
    {
      //read only basic tiff header
      readHeader();
      readImageFrames();
    }




    /**
     * Reads the first eight bytes from the input file, checks if this is a 
     * valid TIFF file and stores byte order and offset of the first image
     * file directory.
     * @throws IOException if there were reading errors
     * @throws WrongFileFormatException if this is not a valid TIFF file
     */
    private void readHeader()
    {

      // the argument to tiffStream.seek must be changed to a variable in the future for
      // this codec to be used to read EXIF information from JPEGs;
      // for some reason, TIFF was chosen for that
      _tiffStream.Seek(0, SeekOrigin.Begin);
      // note: this is the only place where we use tiffStream.readInt()
      // directly; afterwards, the detected byte order
      // is regarded via this class' methods readInt() and readShort()
      // methods

      BinaryReader tiffReader = new BinaryReader(_tiffStream);

      try
      {
        _endian = (TiffDataEndian)tiffReader.ReadInt16(); //read bytes [0-1], [0-1] endian
      }
      catch
      {
        throw new InvalidFileStructureException("Not a TIFF file (does not " +
          "begin with II or MM).");
      }

      //try tiff magic number
      int magic = EndianConverter.ToInt16(tiffReader.ReadInt16(), _endian);

      if (magic != 42 && magic != 43)
        throw new InvalidFileStructureException("Not a TIFF file (does not " +
          "contain tiff magic number (42 or 43)).");
      else if (magic == 43)
        throw new InvalidFileStructureException("Unsupported TIFF file ( " +
          "big tiff is not suported by this codec).");

      _tiffReader = new TiffReader(_tiffStream, _endian);

      _firstIfdOffset = EndianConverter.ToInt32(_tiffReader.ReadInt32(), _endian);
      _nextIfdOffset = _firstIfdOffset;
    }



    
    /// <summary>
    /// this functio steps over all ifd and parses them
    /// </summary>
    private void readImageFrames()
    {
      long nextIfdOffset = _firstIfdOffset;
      ImageFileDirectory ifd;

      ArrayList tmpA = new ArrayList();

      
      TiffMetadata tmpMtdt;

      //step over image directories
      while (nextIfdOffset != 0)
      {
        //seek to first ifd
        _tiffStream.Seek(nextIfdOffset, SeekOrigin.Begin);

        //try create ifd first
        ifd = new ImageFileDirectory(_tiffReader);

        //updated for bitonal images
        tmpMtdt = new TiffMetadata(ifd);
        tmpMtdt.LoadBitonalAsPaletted8bpp = _loadBitonalAsPaletted8bpp;

        //if success, create image for this table
        tmpA.Add(new BlockBitmapFrame(this, tmpMtdt) );

        //get offset to next ifd
        nextIfdOffset = ifd.NextTableOffset;
      }

      _frames = (BlockBitmapFrame[])tmpA.ToArray(typeof(BlockBitmapFrame));
    }


    #endregion private members


  }
}
